package main

import (
	"fmt"
	"log"
	"os"
	"text/template"
)

// text/template实现的是以数据驱动的用于文本输出的模板，简单讲就是将一组文本嵌入另一组文本模板中，返回期望的文本

///////// 例子来自 https://www.cnblogs.com/wanghui-garcia/p/10385062.html

func tRenderString()  {
	str := "world"
	// 建立一个名为 render-string 的模板
	tmp1, err := template.New("render-string").Parse("hello, {{.}}\n")
	if err != nil {
		panic(err)
	}

	// {{.}}：此标签输出当前对象的值， . 在这里即代表str对象
	err = tmp1.Execute(os.Stdout, str)
	if err != nil {
		panic(err)
	}
}

type inventory struct {
	// 必须 是exported fields，Parse()里的field不能写成小写，否则Execute失败
	Material string
	Count uint
}

func tRenderStruct() error {
	sweaters := inventory{"wool", 17}
	// 如果 Count的值也是一个struct对象，可以使用{{.Count.Field1}}来访问其字段
	tmp1, err := template.New("template-struct").Parse("{{.Count}} of {{.Material}}\n")
	if err != nil {
		log.Fatalf("Parse err: %v", err)
	}
	err = tmp1.Execute(os.Stdout, sweaters)
	if err != nil {
		panic(err)
	}
	return err
}

type myStruct struct {
	Say string
	Name string
}
func (my *myStruct) SayHello() string {
	return "world"
}
func (my *myStruct) SayYouName(name string) string {
	return "my name is :" + name
}

func tUseMethodforRendering() error {
	mine := &myStruct{Say: "hello", Name: "student"}
	//先对变量$str1，$str2，$str3赋值，一个是直接将字符串值赋值，另两个是调用函数，将返回值赋值，然后再将变量值输出
	// 这是演示在模板内定义函数，不过一般是使用 Funcs 方法添加到模板里
	tmp1, err := template.New("use-method-for-rendering").Parse(
		"{{$str1 := .Say}}{{$str2 := .SayHello}}{{$str3 := .SayYouName .Name}}{{$str1}} {{$str2}}\n{{$str3}}\n")
	
	err = tmp1.Execute(os.Stdout, mine)
	if err != nil {
		panic(err)
	}
	return err
}

func tUseFuncMapToDefineFunction() error {
	funcMap := template.FuncMap{
		"SayHello": func () string {
			return "hello world"
		},
		"SayYouName": func (name string) string {
			return "my name is: " + name
		},
	}
	name := "boss"
	// 这里之所以不是用 .SayHello， 是因为Execute传入的参数没有SayHello方法
	tmp1, err := template.New("use-funcmap").Funcs(funcMap).Parse("{{SayHello}}\n{{SayYouName .}}\n")
	err = tmp1.Execute(os.Stdout, name)
	return err
}

////////////////////
func tUsePredefinedFunction()  {
	name := "boss"
	// printf相当于 fmt.Printf
	// 是因为text 中已有了 double quotes，所以用 `` 
	// %q 会打印： "boss"

	//tmp1, _ := template.New("test").Parse("{{printf \"tUsePredefinedFunction: %q\n\" .}}") // 这样不行
	tmp1, _ := template.New("test").Parse(`{{printf "tUsePredefinedFunction: %q\n" .}}`)
	tmp1.Execute(os.Stdout, name)
}

/////////////////////
// {{ }}内的操作我们将其称作pipelines
func tUseIfElse() {
	// 创建一个模板
	// {{- else}} 等处的 - 表示消除因{{else}} 存在导致的空行
	// {{with .Gift}} 表示如果Gift不为空的话，则打印下面的句子，与{{end}} 配套
	const letter = `
---------------------------------
Dear {{.Name}},
{{- if .Attended}}
It was a pleasure to see you at the wedding.
{{- else}}
It is a shame you couldn't make it to the wedding.
{{- end}}
{{with .Gift -}}
Thank you for the lovely {{.}}.
{{end}}
Best wishes,
Lewis Chan
`	
	type recipient struct {
		Name, Gift string
		Attended bool
	}
	var recipients = []recipient{
		{"Jim", "apple", true},
		{"Lucy", "orange", false},
	}

	// Must函数用于包装返回(*Template, error)的函数/方法调用，它会在err非nil时panic，一般用于变量初始化
	t := template.Must(template.New("letter").Parse(letter))
	for _, r := range recipients {
		err := t.Execute(os.Stdout, r)
		if err != nil {
			log.Println("executing template: ", err)
		}
	}
}

////////////////////////////////////////
func tUseRange()  {
	//创建一个模版
	//{{range $i, $v := .Var}} //显示得到遍历的index和value\
	//{{range .Var}} //没有显示去获取遍历得到的index和value，这时候要获得value值，使用{{.}}表示
	//{{range .slice}} //如果想要在range...end中访问非遍历得到的value，即外部的其他值，则在前面添加$来表示
	rangeTemplate := `
###########################
{{- if .Kind}}
{{range $i, $v := .MapContent}}
{{$i}} => {{$v}} , {{$.OutsideContent}}
{{- end}}
{{else}}
{{range .MapContent}}
{{.}} , {{$.OutsideContent}}
{{- end}}
{{end}}`

    str1 := []string{"this is the first range", "use its index and value"}
    str2 := []string{"this is the second range", "do not use its index and value"}

    type Content struct {
        MapContent []string
        OutsideContent string
        Kind bool
    }
    var contents = []Content{
        {str1, "this is the first outside content", true},
        {str2, "this is the second outside content", false},
	}
	// Create a new template and parse the letter into it.
    t := template.Must(template.New("range").Parse(rangeTemplate))

    // Execute the template for each recipient.
    for _, c := range contents {
        err := t.Execute(os.Stdout, c)
        if err != nil {
            log.Println("executing template:", err)
        }
    }
}

/////////////////////////
//{{template "name"}}: 执行名为name的模板，提供给模板的参数为nil，如模板不存在输出为""。当然首先要使用{{define "name"}}{{end}}定义好该模版
//{{template "name" pipeline}}: 执行名为name的模板，提供给模板的参数为pipeline的值。将管道的值赋给子模板中的"."（即"{{.}}"），即{{template "name" .}}
func tNestedTemplate() {
	//创建一个模版
	// T3中嵌套T1,T2，最后执行T3
    templateContent := `{{define "T1"}}ONE{{end}}{{define "T2"}}TWO{{end}}{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}{{template "T3"}}`

    // Create a new template and parse the letter into it.
    t := template.Must(template.New("template").Parse(templateContent))

    // Execute the template for each recipient.
    err := t.Execute(os.Stdout, nil)
    if err != nil {
        log.Println("executing template:", err)
	}
	
	fmt.Println()

	///////// 与上面写法等价，使用New
	useNewToNested := func ()  {
		//创建一个模版
		template1 := "ONE"
		template2 := "TWO"
		template3 := `{{template "T1"}} {{template "T2"}}`
	
		// Create a new template and parse the letter into it.
		t := template.Must(template.New("T1").Parse(template1)) // Must 本身返回 template，可级联调用
		t = template.Must(t.New("T2").Parse(template2)) //t.New 是表示创建一个与t相关联的template，此为多模板
		t = template.Must(t.New("T3").Parse(template3))
	
		// Execute the template for each recipient.
		err := t.Execute(os.Stdout, nil)
		if err != nil {
			log.Println("executing template:", err)
		}
	}
	useNewToNested()
}

////////////////////
func tMultiTemplate()  {
	sweaters := inventory{"wool", 17}
    template1 := "{{.Count}} of {{.Material}}\n"
    template2 := "{{.Material}} of {{.Count}}\n"

    tmpl := template.Must(template.New("T1").Parse(template1))
    fmt.Println(tmpl.Name()) //T1
    tmpl = template.Must(tmpl.New("T2").Parse(template2))
    fmt.Println(tmpl.Name()) //T2

	// 执行与tmp1相关联的，且名为T1的模板
    err := tmpl.ExecuteTemplate(os.Stdout, "T1", sweaters)//返回 17 of wool
    if err != nil { panic(err) }
    err = tmpl.ExecuteTemplate(os.Stdout, "T2", sweaters)//返回 wool of 17
    if err != nil { panic(err) }

    tmpl = tmpl.Lookup("T1")
    fmt.Println(tmpl.Name()) //T1

    mapTemplate := tmpl.Templates()
    for _, v := range mapTemplate{ //先得到T2，再得到T1
        fmt.Println(v.Name())
    }
}

////////////////////////// 
func tParseFiles()  {
	sweaters := inventory{"This is template using parse files/globfiles", 17}

	// ParseFiles函数创建一个模板并解析filenames指定的文件里的模板定义。返回的模板的名字是第一个文件的文件名（不含扩展名），内容为解析后的第一个文件的内容
	// 相当于以前的 New + Parse
	tmpl, err := template.ParseFiles("templateContent.txt")
	if err != nil { panic(err) }
	fmt.Println("tmp1 name: " + tmpl.Name())
	err = tmpl.Execute(os.Stdout, sweaters)
	if err != nil { panic(err) }

	fmt.Println("------------ using glob file")
	useGlobfile := func ()  {
		tmpl := template.Must(template.ParseGlob("*.txt"))

		mapTemplate := tmpl.Templates()
		for _, v := range mapTemplate{ //先得到anotherTemplate.txt，再得到templateContent.txt
			fmt.Println("printing template name:" + v.Name())
		}

		err := tmpl.ExecuteTemplate(os.Stdout, "templateContent.txt", sweaters)
		if err != nil { panic(err) }
		err = tmpl.ExecuteTemplate(os.Stdout, "anotherTemplate.txt", sweaters)
		if err != nil { panic(err) }
	}
	useGlobfile()
}

func tTemplateExample()  {
	//tRenderString()
	//tRenderStruct()
	tUseMethodforRendering()
	tUseFuncMapToDefineFunction()
	tUsePredefinedFunction()
	tUseIfElse()
	tUseRange()
	tNestedTemplate()
	tMultiTemplate()
	tParseFiles()
}